#include "customSkinDeformer.h"

#include <maya/MFnMatrixAttribute.h>
#include <maya/MMatrixArray.h>
#include <maya/MFnMatrixData.h>
#include <maya/MGlobal.h>
#include <maya/MItGeometry.h>
#include <maya/MPoint.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnTransform.h>
#include <maya/MFnNumericAttribute.h>


MTypeId customSkinDeformer::id = MTypeId(0x7f7f7);
MString customSkinDeformer::nodeName = "xsCustomSkin";
MObject customSkinDeformer::preBindGeomMatrix = MObject();
MObject customSkinDeformer::influenceMatrices = MObject();
MObject customSkinDeformer::preBindMatrices = MObject();
MObject customSkinDeformer::affetcOutputGeomAttr = MObject();


customSkinDeformer::customSkinDeformer()
{
}

customSkinDeformer::~customSkinDeformer()
{
}

void* customSkinDeformer::creator()
{
	return new customSkinDeformer();
}

MStatus customSkinDeformer::initialize()
{
	MFnMatrixAttribute mfn_MatrixAttr;
	customSkinDeformer::preBindGeomMatrix = mfn_MatrixAttr.create("preBindGeomMatrix", "preGeomMatrix", MFnMatrixAttribute::Type::kDouble);
	mfn_MatrixAttr.setWritable(true);
	mfn_MatrixAttr.setReadable(true);
	mfn_MatrixAttr.setKeyable(false);
	mfn_MatrixAttr.setHidden(true);
	
	customSkinDeformer::preBindMatrices = mfn_MatrixAttr.create("preBindMatrices", "preMatrices", MFnMatrixAttribute::Type::kDouble);
	mfn_MatrixAttr.setArray(true);
	mfn_MatrixAttr.setWritable(true);
	mfn_MatrixAttr.setReadable(true);
	mfn_MatrixAttr.setKeyable(false);
	mfn_MatrixAttr.setHidden(true);
	
	customSkinDeformer::influenceMatrices = mfn_MatrixAttr.create("influenceMatrices", "ifMatrices", MFnMatrixAttribute::Type::kDouble);
	mfn_MatrixAttr.setArray(true);
	mfn_MatrixAttr.setWritable(true);
	mfn_MatrixAttr.setReadable(true);
	mfn_MatrixAttr.setKeyable(false);
	mfn_MatrixAttr.setHidden(true);

	MFnNumericAttribute mfn_numericAttr;
	customSkinDeformer::affetcOutputGeomAttr = mfn_numericAttr.create("affetcOutputGeomAttr", "affetcOutputGeom", MFnNumericData::kInt);
	mfn_numericAttr.setWritable(true);
	mfn_numericAttr.setReadable(true);
	mfn_numericAttr.setKeyable(false);
	mfn_numericAttr.setHidden(true);
	

	addAttribute(customSkinDeformer::preBindGeomMatrix);
	addAttribute(customSkinDeformer::preBindMatrices);
	addAttribute(customSkinDeformer::influenceMatrices);
	addAttribute(customSkinDeformer::affetcOutputGeomAttr);
	
	attributeAffects(customSkinDeformer::preBindMatrices, customSkinDeformer::outputGeom);
	attributeAffects(customSkinDeformer::influenceMatrices, customSkinDeformer::outputGeom);
	attributeAffects(customSkinDeformer::affetcOutputGeomAttr, customSkinDeformer::outputGeom);
	
	return MStatus::kSuccess;
}

MStatus customSkinDeformer::deform(MDataBlock& block, MItGeometry& iter, const MMatrix& mat, unsigned int multiIndex)
{
	MStatus status;

	MDataHandle envelopeHandle = block.inputValue(envelope);
	float envelopeMask = envelopeHandle.asFloat();
	if (envelopeMask == 0.f)
	{
		return MStatus::kSuccess;
	}
	
	// get influence transform
	MArrayDataHandle influenceTransformsHandle = block.inputArrayValue(customSkinDeformer::influenceMatrices);
	unsigned int numInfluenceTransforms = influenceTransformsHandle.elementCount();
	if (numInfluenceTransforms == 0)
	{
		// no transform nothing to do
		return MStatus::kSuccess;
	}

	
	MMatrixArray transforms;
	for (unsigned int i = 0; i < numInfluenceTransforms; ++i)
	{
		transforms.append(influenceTransformsHandle.inputValue().asMatrix());
		influenceTransformsHandle.next();
	}
	
	MArrayDataHandle preBindMatrixHandle = block.inputArrayValue(customSkinDeformer::preBindMatrices);
	if (preBindMatrixHandle.elementCount() > 0)
	{
		for (auto& transform:transforms)
		{
			transform = preBindMatrixHandle.inputValue().asMatrix() * transform;
			preBindMatrixHandle.next();
		}
	}

	MMatrix geomWorldMatrix = block.inputValue(customSkinDeformer::preBindGeomMatrix).asMatrix();
	
	// Iterate each point
	for (;!iter.isDone(); iter.next())
	{
		// pre bind local space coord
		MPoint pos = iter.position();
		// world space pos
		pos *= geomWorldMatrix;
		MPoint skined;
	
		// compute skin
		for (unsigned int i = 0; i < numInfluenceTransforms; ++i)
		{
			// get weights for this point
			double weight = weightValue(block, iter.index(), i);
			
			skined += (pos * transforms[i]) * weight * envelopeMask;
		}
		// set position
		skined += pos * (1 - envelopeMask);
		skined *= geomWorldMatrix.inverse();
		iter.setPosition(skined);
	}

	return MStatus::kSuccess;
}


